#!/bin/bash

# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Load common CrOS utilities.  Inside the chroot this file is installed in
# /usr/lib/crosutils.  Outside the chroot we find it relative to the script's
# location.
. "$(dirname "$0")/common.sh" || exit 1

# Script must run inside the chroot.
assert_inside_chroot

# May not be run as root.
assert_not_root_user

DEFINE_string version "" \
  "Assume current chroot version is this."
DEFINE_boolean init_latest "${FLAGS_FALSE}" \
  "Create the version file with the latest version if it doesn't exist"
DEFINE_boolean skipfirst "${FLAGS_FALSE}" \
  "Skip the first upgrade. This may be dangerous."

FLAGS "$@" || exit 1

VERSION_HOOKS_DIR="$(dirname "$(readlink -f "${0}")")/chroot_version_hooks.d"

update_version() {
  sudo touch "${CHROOT_VERSION_FILE}"
  sudo chown "${USER}" "${CHROOT_VERSION_FILE}"
  echo "${1}" > "${CHROOT_VERSION_FILE}"
}

######################################################################

# Sanity checks:
if [ -n "${FLAGS_version}" ] && \
    ( [ "${FLAGS_skipfirst}" == "${FLAGS_TRUE}" ] || \
    [ "${FLAGS_init_latest}" == "${FLAGS_TRUE}" ] ); then
  error "The option --version cannot be combined with either"
  error "--skipfirst or --init_latest."
  exit 1
fi

if [ "${FLAGS_skipfirst}" == "${FLAGS_TRUE}" ] &&
    [ "${FLAGS_init_latest}" == "${FLAGS_TRUE}" ]; then
  error "--skipfirst and --init_latest cannot be combined."
  exit 1
fi

# Latest version is the version of last upgrade.d file.
# Name format is ${number}_${short_description}
# Versions must be -n sorted, that is, the first continuous sequence
# of numbers is what counts. 12_ is before 111_, etc.
LATEST_VERSION=$(
  cd "${VERSION_HOOKS_DIR}"
  ls [0-9]*_* | cut -d_ -f1 | sort -rn | head -n1)

# If the file does not exist at all, chroot is old and does not have a version.
# default goes here
if ! [ -f "${CHROOT_VERSION_FILE}" ]; then
  update_version 0
fi

CHROOT_VERSION=$(<"${CHROOT_VERSION_FILE}")
# Check if version is a number.
if ! [ "${CHROOT_VERSION}" -ge "0" ] &> /dev/null; then
  error "Your chroot version file ${CHROOT_VERSION_FILE} is bogus: "
  error "${CHROOT_VERSION}"
  exit 1
fi

if [[ "${FLAGS_init_latest}" == "${FLAGS_TRUE}" ]]; then
  if [[ "${CHROOT_VERSION}" -eq "0" ]]; then
    info "Initializing chroot to version ${LATEST_VERSION}"
    update_version "${LATEST_VERSION}"
  else
    info "Chroot is already initialized to ${CHROOT_VERSION}"
  fi
  exit 0
fi

if [ "${FLAGS_skipfirst}" == "${FLAGS_TRUE}" ]; then
  if [ "${CHROOT_VERSION}" -lt "${LATEST_VERSION}" ]; then
    # if the new one is latest, this becomes noop
    CHROOT_VERSION=$(expr ${CHROOT_VERSION} + 1)
    update_version "${CHROOT_VERSION}"
  else
    error "Nothing to skip"
    exit 1
  fi
fi

if [ -n "${FLAGS_version}" ]; then
  # Check if it's a number.
  if ! [ "${FLAGS_version}" -ge "0" ] &> /dev/null; then
    error "Trying to force invalid version: ${FLAGS_version}"
    exit 1
  fi

  if [ "${FLAGS_version}" -gt "${LATEST_VERSION}" ]; then
    error "Forcing nonexistant version: ${FLAGS_version}"
    exit 1
  fi

  CHROOT_VERSION="${FLAGS_version}"
fi


if [ "${LATEST_VERSION}" -gt "${CHROOT_VERSION}" ]; then
  info "Old chroot version (${CHROOT_VERSION}) found, running upgrade hooks"

  pushd "${VERSION_HOOKS_DIR}" 1> /dev/null
  for n in $(seq "$(expr ${CHROOT_VERSION} + 1)" "${LATEST_VERSION}"); do
    hook=(${n}_*)

    # Sanity check: if there are multiple ${n}_* files, then CL's landed
    # at the same time and people didn't notice.  Let's notice for them.
    if [ ${#hook[@]} -gt 1 ]; then
      error "Fatal: Upgrade ${n} has multiple hooks:"
      error "   ${hook[*]}"
      error "Connor MacLeod knows: There can be only one."
      exit 1
    fi
    hook=${hook[0]}

    # Deprecation check; Deprecation can be done by removing old upgrade
    # scripts and causing too old chroots to have to start over.
    # Upgrades have to form a continuous sequence.
    if ! [ -f ${hook} ]; then
      error "Fatal: Upgrade ${n} doesn't exist."
      error "Your chroot is so old, that some updates have been deprecated!"
      error "You need to re-create it!"
      exit 1
    fi

    info "Rollup ${hook}"

    # Attempt the upgrade.
    # NOTE: We source the upgrade scripts because:
    # 1) We can impose set -something on them.
    # 2) They can reuse local variables and functions (fe. from common.sh)
    # 3) They're allowed to use VERSION_HOOKS_DIR and CHROOT_VERSION_FILE.
    # Note that the upgrade scripts have to be subshelled to protect ourselves,
    # else a script running exit would stop the upgrade process entirely.
    if ! ( source ${hook} ); then
      error "Fatal: failed to upgrade ${n}!"
      exit 1
    fi
    # Each upgrade is atomic. If a middle upgrade fails, we won't retry
    # all the ones that passed on a previous run.
    update_version "${n}"
  done
  popd 1> /dev/null
elif [ "${LATEST_VERSION}" -lt "${CHROOT_VERSION}" ]; then
  error "Fatal: Missing upgrade hook for ${CHROOT_VERSION}"
  error "Chroot version is too new. Consider running cros_sdk --replace"
  exit 1
fi

command_completed
